#!/usr/bin/env ruby
# -*- coding: binary -*-
#
# $Id$
# $Revision$
#

$stderr.puts "[!] ************************************************************************"
$stderr.puts "[!] *               The utility msfencode is deprecated!                   *"
$stderr.puts "[!] *              It will be removed on or about 2015-06-08               *"
$stderr.puts "[!] *                   Please use msfvenom instead                        *"
$stderr.puts "[!] *  Details: https://github.com/rapid7/metasploit-framework/pull/4333   *"
$stderr.puts "[!] ************************************************************************"

msfbase = __FILE__
while File.symlink?(msfbase)
  msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end

$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib')))
require 'msfenv'



$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']

require 'rex'
require 'msf/ui'
require 'msf/base'

OutStatus = "[*] "
OutError  = "[-] "

# Load supported formats
supported_formats = Msf::Simple::Buffer.transform_formats + Msf::Util::EXE.to_executable_fmt_formats

$args = Rex::Parser::Arguments.new(
  "-h" => [ false, "Help banner"                                                       ],
  "-l" => [ false, "List available encoders"                                           ],
  "-v" => [ false, "Increase verbosity"                                                ],
  # input/output
  "-i" => [ true, "Encode the contents of the supplied file path"                      ],
  "-m" => [ true, "Specifies an additional module search path"                         ],
  "-o" => [ true, "The output file"                                                    ],
  # architecture/platform
  "-a" => [ true, "The architecture to encode as"                                      ],
  "-p" => [ true, "The platform to encode for"                                         ],
  # format options
  "-t" => [ true, "The output format: #{supported_formats.join(',')}"                  ],
  # encoder options
  "-e" => [ true, "The encoder to use"                                                 ],
  "-n" => [ false, "Dump encoder information"                                          ],
  "-b" => [ true, "The list of characters to avoid: '\\x00\\xff'"                      ],
  "-s" => [ true, "The maximum size of the encoded data"                               ],
  "-c" => [ true, "The number of times to encode the data"                             ],
  # EXE generation options
  "-d" => [ true, "Specify the directory in which to look for EXE templates"           ],
  "-x" => [ true, "Specify an alternate executable template"                           ],
  "-k" => [ false, "Keep template working; run payload in new thread (use with -x)"    ]
)

#
# Dump the list of encoders
#
def dump_encoders(arch = nil)
  tbl = Rex::Ui::Text::Table.new(
    'Indent'  => 4,
    'Header'  => "Framework Encoders" + ((arch) ? " (architectures: #{arch})" : ""),
    'Columns' =>
      [
        "Name",
        "Rank",
        "Description"
      ])
  cnt = 0

  $framework.encoders.each_module(
    'Arch' => arch ? arch.split(',') : nil) { |name, mod|
    tbl << [ name, mod.rank_to_s, mod.new.name ]

    cnt += 1
  }

  (cnt > 0) ? "\n" + tbl.to_s + "\n" : "\nNo compatible encoders found.\n\n"
end

#
# Returns the list of encoders to try
#
def get_encoders(arch, encoder)
  encoders = []

  if (encoder)
    encoders << $framework.encoders.create(encoder)
  else
    $framework.encoders.each_module_ranked(
      'Arch' => arch ? arch.split(',') : nil) { |name, mod|
      encoders << mod.new
    }
  end

  encoders
end

#
# Nuff said.
#
def usage
  $stderr.puts("\n" + "    Usage: #{$0} <options>\n" + $args.usage)
  exit
end

def write_encoded(buf)
  if (not $output)
    $stdout.write(buf)
  else
    File.open($output, "wb") do |fd|
      fd.write(buf)
    end
  end
end

# Defaults
verbose  = 0
cmd      = "encode"
arch     = nil
badchars = ''
space    = nil
encoder  = nil
fmt      = nil
input    = $stdin
options  = ''
delim    = '_|_'
output   = nil
ecount   = 1
plat     = nil

altexe   = nil
inject   = false
exedir   = nil  # use default

# Parse the argument and rock it
$args.parse(ARGV) { |opt, idx, val|
  case opt
    when "-i"
      begin
        input = File.open(val, 'rb')
      rescue
        $stderr.puts(OutError + "Failed to open file #{val}: #{$!}")
        exit
      end
    when "-m"
      $framework.modules.add_module_path(val)
    when "-l"
      cmd = "list"
    when "-n"
      cmd = "dump"
    when "-a"
      arch = val
    when "-c"
      ecount = val.to_i
    when "-b"
      badchars = Rex::Text.hex_to_raw(val)
    when "-p"
      plat = Msf::Module::PlatformList.transform(val)
    when "-s"
      space = val.to_i
    when "-t"
      if supported_formats.include?(val)
        fmt = val
      else
        $stderr.puts(OutError + "Invalid format: #{val}")
        exit
      end
    when "-o"
      $output = val
    when "-e"
      encoder = val

    when "-d"
      exedir = val
    when "-x"
      altexe = val
    when "-k"
      inject = true

    when "-h"
      usage

    when "-v"
      verbose += 1

    else
      if (val =~ /=/)
        options += ((options.length > 0) ? delim : "") + "#{val}"
      end
  end
}


if(not fmt and output)
  pre,ext = output.split('.')
  if(ext and not ext.empty?)
    fmt = ext
  end
end

if inject and not altexe
  $stderr.puts "[*] Error: the injection option must use a custom EXE template via -x, otherwise the injected payload will immediately exit when the main process dies."
  exit(1)
end
exeopts = {
  :inject => inject,
  :template => altexe,
  :template_path => exedir
}

# Initialize the simplified framework instance.
$framework = Msf::Simple::Framework.create(
  :module_types => [ Msf::MODULE_ENCODER, Msf::MODULE_NOP ],
  'DisableDatabase' => true
)

# Get the list of encoders to try
encoders = get_encoders(arch, encoder)

# Process the actual command
case cmd
  when "list"
    $stderr.puts(dump_encoders(arch))
  when "dump"
    enc = encoder ? $framework.encoders.create(encoder) : nil

    if (enc)
      $stderr.puts(Msf::Serializer::ReadableText.dump_module(enc))
    else
      $stderr.puts(OutError + "Invalid encoder specified.")
    end
  when "encode"
    input.binmode  # ensure its in binary mode
    buf = input.read

    encoders.each { |enc|
      next if not enc
      begin
        # Imports options
        enc.datastore.import_options_from_s(options, delim)

        skip = false
        eout = buf.dup
        raw  = nil

        1.upto(ecount) do |iteration|

          # Encode it up
          raw = enc.encode(eout, badchars, nil, plat)

          # Is it too big?
          if (space and space > 0 and raw.length > space)
            $stderr.puts(OutError + "#{enc.refname} created buffer that is too big (#{raw.length})")
            skip = true
            break
          end

          # Print it out
          $stderr.puts(OutStatus + "#{enc.refname} succeeded with size #{raw.length} (iteration=#{iteration})\n\n")
          eout = raw
        end

        next if skip

        output = Msf::Util::EXE.to_executable_fmt($framework, arch, plat, raw, fmt, exeopts)

        if not output
          fmt ||= "ruby"
          output = Msf::Simple::Buffer.transform(raw, fmt)
        end

        if exeopts[:fellback]
          $stderr.puts(OutError + "Warning: Falling back to default template: #{exeopts[:fellback]}")
        end

        write_encoded(output)

        exit

      #
      # These exception codes are fatal, we shouldn't expect them to succeed on the next
      # iteration, nor the next encoder.
      #
      rescue ::Errno::ENOENT, ::Errno::EINVAL
        $stderr.puts(OutError + "#{enc.refname} failed: #{$!}")
        break

      rescue => e
        $stderr.puts(OutError + "#{enc.refname} failed: #{e}")
        if verbose > 0
          e.backtrace.each { |el|
            $stderr.puts(OutError + el.to_s)
          }
        end
      end
    }

    $stderr.puts(OutError + "No encoders succeeded.")
end
